package io.kiki.stack.http.feign.form.util;

import io.kiki.stack.http.feign.form.FormProperty;
import lombok.*;
import lombok.experimental.FieldDefaults;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.rmi.UnexpectedException;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;

import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isStatic;
import static lombok.AccessLevel.PRIVATE;

/**
 * An utility class to work with POJOs.
 *
 * @author Artem Labazin
 */
public final class PojoUtil {

    private PojoUtil() throws UnexpectedException {
        throw new UnexpectedException("It is not allowed to instantiate this class");
    }

    public static boolean isUserPojo(@NonNull Object object) {
        val type = object.getClass();
        val packageName = type.getPackage().getName();
        return !packageName.startsWith("java.");
    }

    public static boolean isUserPojo(@NonNull Type type) {
        val typeName = type.toString();
        return !typeName.startsWith("class java.");
    }

    @SneakyThrows
    public static Map<String, Object> toMap(@NonNull Object object) {
        val result = new HashMap<String, Object>();
        val type = object.getClass();
        for (val field : type.getDeclaredFields()) {
            val modifiers = field.getModifiers();
            if (isFinal(modifiers) || isStatic(modifiers)) {
                continue;
            }
            field.setAccessible(true);

            val fieldValue = field.get(object);
            if (fieldValue == null) {
                continue;
            }

            val propertyKey = field.isAnnotationPresent(FormProperty.class) ? field.getAnnotation(FormProperty.class).value() : field.getName();

            result.put(propertyKey, fieldValue);
        }
        return result;
    }

    @Setter
    @NoArgsConstructor
    @FieldDefaults(level = PRIVATE)
    private static final class SetAccessibleAction implements PrivilegedAction<Object> {

        Field field;

        @Override
        public Object run() {
            field.setAccessible(true);
            return null;
        }
    }
}
